package fr.cephb.joperon.core;

import java.io.File;
import java.io.FileNotFoundException;
import java.util.Iterator;
import java.util.List;
import java.util.Vector;
import java.util.logging.Logger;




import com.sleepycat.db.Cursor;
import com.sleepycat.db.CursorConfig;
import com.sleepycat.db.Database;
import com.sleepycat.db.DatabaseConfig;
import com.sleepycat.db.DatabaseEntry;
import com.sleepycat.db.DatabaseException;
import com.sleepycat.db.DatabaseType;
import com.sleepycat.db.Environment;
import com.sleepycat.db.ErrorHandler;
import com.sleepycat.db.LockMode;
import com.sleepycat.db.OperationStatus;
import com.sleepycat.db.SecondaryConfig;
import com.sleepycat.db.SecondaryCursor;
import com.sleepycat.db.SecondaryDatabase;
import com.sleepycat.db.Transaction;

import fr.cephb.berkeley.ByteReader;
import fr.cephb.berkeley.ByteWriter;
import fr.cephb.berkeley.DBUtils;
import fr.cephb.joperon.core.bio.Assembly;
import fr.cephb.joperon.core.bio.Chromosome;
import fr.cephb.joperon.core.bio.Organism;
import fr.cephb.joperon.core.iterator.AbstractIterator;
import fr.cephb.joperon.core.iterator.DatabaseEntriesIterator;
import fr.cephb.joperon.core.iterator.DatabaseKeyIterator;
import fr.cephb.joperon.core.iterator.DatabaseValueIterator;
import fr.cephb.joperon.core.iterator.KeyRangeIterator;
import fr.cephb.lang.UInt32;
import fr.cephb.util.Pair;

/**
 * Basic implementation of a Database Wrapper in Operon
 * with a key K and a Value V
 * @author lindenb
 *
 * @param <K> the K in berkeleyDB, must be wrapped in a Comparable
 * @param <V> the Value in berkeleyDB
 */
public abstract class OperonDB<K extends Comparable<K>,V>
implements Iterable<Pair<K,V>>
{
/** database wrapped */
protected Database database=null;
private DatabaseConfig dbConfig=null;
private Operon myoperon;

/** an iterator over a organism/chromosome/start-end in a database using a Fid as a key */
public class FidEntriesIterator
	extends KeyRangeIterator
	{
	private Fid fidEnd;
	/**
	 * Constructor
	 * @param org organisme
	 * @param chr chromosome
	 * @param start start index
	 * @param end end index
	 * @throws DatabaseException
	 */
	public FidEntriesIterator(Organism org,Chromosome chr,int start,int end)  throws DatabaseException
		{
		super(OperonDB.this.cursor(),
			new DatabaseEntry(new Fid(org,chr,start).as0EndedByteArray())
			);
		this.fidEnd= new Fid(org,chr,end);
		}
	@Override
	protected boolean isInRange(DatabaseEntry entry) {
		Fid x= new Fid(entry);
		return x.compareTo(this.fidEnd)<0;
		}
	}



/** 
 * Iterator over the <b>Values</b> of this database
 * @param operon
 */
public class ValueIterator
extends DatabaseValueIterator<V>
	{
	public ValueIterator(AbstractIterator<Pair<DatabaseEntry,DatabaseEntry>> delegate)
		{
		super(delegate);
		}
	
	@Override
	protected V extractFromEntries(DatabaseEntry key, DatabaseEntry value)
			throws DatabaseException {
		return OperonDB.this.readValue(key, value);
		}
	}


/** 
 * Iterator over the <b>Keys</b> of this database
 * @param operon
 */
public class KeyIterator
extends DatabaseKeyIterator<K>
	{
	public KeyIterator(AbstractIterator<Pair<DatabaseEntry,DatabaseEntry>> delegate)
		{
		super(delegate);
		}
	@Override
	protected K extractFromKey(DatabaseEntry key,DatabaseEntry value) throws DatabaseException {
		return OperonDB.this.readKey(key, value);
		}
	}



/** 
 * Iterator over the <b>Keys</b> and the <b>Values</b> of this database
 * @param operon
 */
public abstract class KeyValueIterator
extends AbstractIterator<Pair<K,V>>
	{
	
	}

/** 
 * Iterator over the <b>Keys</b> and the <b>Values</b> of this database
 * @param operon
 */
public class BetweenIterator
extends KeyValueIterator
	{
	private K end;
	private boolean includeLast;
	private Cursor cursor;
	boolean first=true;
	DatabaseEntry keyEntry;
	DatabaseEntry dataEntry= new DatabaseEntry();
	BetweenIterator(K start,K end,boolean includeLast)
		throws DatabaseException
		{
		this.end=end;
		this.includeLast=includeLast;
		this.keyEntry= keyToEntry(start);
		try
			{
			this.cursor= cursor();
			}
		catch(DatabaseException err)
			{
			err.printStackTrace();
			close();
			throw err;
			}
		finally
			{
			
			}
		}
	
	BetweenIterator() throws DatabaseException
		{
		this.end=null;
		this.keyEntry=new DatabaseEntry();
		try
			{
			this.cursor= cursor();
			}
		catch(DatabaseException err)
			{
			err.printStackTrace();
			close();
			throw err;
			}
		finally
			{
			
			}
		}
	
	
	@Override
	public void close()
		{
		super.close();
		if(this.cursor!=null)
			{
			try {
				this.cursor.close();
			} catch (Exception e) {
				}
			this.cursor=null;
			}
		}

	@Override
	public boolean hasNext()
		{
		if(super.eofMet==true) return false;
		if(super._hasNextTested) return _hasNext;
		super._hasNextTested=true;
		OperationStatus status;
		try
			{
			if(first)
				{
				if(this.end!=null)
					{
					status=this.cursor.getSearchKeyRange(keyEntry, dataEntry, LockMode.DEFAULT);
					}
				else
					{
					status= this.cursor.getFirst(keyEntry, dataEntry,LockMode.DEFAULT);
					}
				first=false;
				}
			else if(isAllowingDuplicates())
				{
				status=this.cursor.getNext(keyEntry, dataEntry, LockMode.DEFAULT);
				}
			else
				{
				status=this.cursor.getNext(keyEntry, dataEntry, LockMode.DEFAULT);
				}
			
			if(status!=OperationStatus.SUCCESS)
				{
				close();
				this._hasNext=false;
				}
			else
				{
				//we're looking in a range
				if(this.end!=null)
					{
					K curr= readKey(keyEntry, dataEntry);
					
					int n= curr.compareTo(this.end);
					//System.err.println("Je compare end:"+this.end+" vs curr:"+curr+" cmp="+n);
					if(n==0)
						{
						this._hasNext=this.includeLast;
						}
					else
						{
						this._hasNext = n<0;
						}
					}
				else//we're getting all the values
					{
					this._hasNext=true;
					}
				if(this._hasNext)
					{
					this._nextValue= new Pair<K, V>(
						readKey(keyEntry,dataEntry),
						readValue(keyEntry,dataEntry)
						);
					}
				else
					{
					close();
					}
				}
			}
		catch(DatabaseException err)
			{
			err.printStackTrace();
			close();
			_hasNext=false;
			}
		
		
		
		return _hasNext;
		}

	@Override
	public Pair<K,V> next()
		{
		if(!_hasNextTested) hasNext();
		if(!_hasNext)
			{
			close();
			throw new IllegalStateException("no next value in iterator");
			}
		_hasNextTested=false;
		_hasNext=false;
		return _nextValue;
		}
	}




/**
 * Iterator in a given key range
 * @author lindenb
 *
 */
public class KeyRangeValueIterator
extends KeyRangeIterator
	{
	private K upper;
	private boolean includeLast=false;
	/**
	 * Constructor
	 * @param lower including range
	 * @param upper excluding range
	 * @param includeLast include last item in range
	 * @throws DatabaseException
	 */
	public KeyRangeValueIterator(K lower,K upper,boolean includeLast) throws DatabaseException
		{
		super(
				OperonDB.this.cursor(),
				OperonDB.this.keyToEntry(lower));
		this.upper=upper;
		this.includeLast=includeLast;
		}
	
	/**
	 * Constructor with includeLast=false
	 * @param lower including range
	 * @param upper excluding range
	 * @param includeLast include last item in range
	 * @throws DatabaseException
	 */
	public KeyRangeValueIterator(K lower,K upper) throws DatabaseException
		{
		this(lower,upper,false);
		}
	
	@Override
	protected boolean isInRange(DatabaseEntry entry)throws DatabaseException
		{
		int i= OperonDB.this.readKey(entry).compareTo(this.upper);
		if(i==0)
			{
			return includeLast?true:false;
			}
		return i<0;
		}	
	}


/** Specialized Iterator for the Secondary Databases */
abstract static class AbstractSecondaryDatabase
		<
		SECONDARYKEY extends Comparable<SECONDARYKEY>,
		K extends Comparable<K>,V
		>
	extends OperonDBDuplicateValue<SECONDARYKEY, V>
	{
	private OperonDB<K,V> owner;
	protected AbstractSecondaryDatabase(OperonDB<K,V> owner)
		{
		super(owner.getOperon());
		this.owner=owner;
		}
	
	public OperonDB<K,V> getOwner()
		{
		return owner;
		}
	
	@Override
	/** get a cursor for this 2nd database */
	public Cursor cursor(Transaction txn, CursorConfig config)
			throws DatabaseException {
		return getSecondaryDatabase().openSecondaryCursor(txn, config);
		}
	
	/** get a cursor for this 2nd database */
	public SecondaryCursor secondaryCursor(Transaction txn, CursorConfig config)throws DatabaseException
		{
		return SecondaryCursor.class.cast(cursor());
		}
	
	/** get a cursor for this 2nd database */
	public SecondaryCursor secondaryCursor()throws DatabaseException
		{
		return secondaryCursor(null,null);
		}
	
	public SecondaryConfig getSecondaryConfig() throws DatabaseException
		{
		return SecondaryConfig.class.cast(getDatabaseConfig());
		}
	
	@Override
	protected DatabaseConfig createConfig() throws DatabaseException
		{
		SecondaryConfig cfg = new SecondaryConfig();
		cfg.setAllowCreate(false);
		cfg.setReadOnly(true);
		cfg.setErrorHandler(new ErrorHandler()
			{
			@Override
			public void error(Environment environment, String errpfx, String msg) {
				System.err.println(""+errpfx+": An error occured for "+getName()+":"+msg);
				}
			});
		//cfg.setUnsortedDuplicates(isAllowingDuplicates());
		return cfg;
		}
	
	@Override
	/** open this secondary database */
	public OperonDB<SECONDARYKEY, V> open() throws DatabaseException,
			FileNotFoundException {
		if(super.database!=null) return this;
		log().info("opening secondaryDB "+getOperon().getEnvironment().getHome()+"/"+getName());
		this.database= getOperon().getEnvironment().openSecondaryDatabase(null, getName(), null,getOwner().getDatabase(), getSecondaryConfig());
		return this;
		}
	
	public SecondaryDatabase getSecondaryDatabase()
		{
		return SecondaryDatabase.class.cast(getDatabase());
		}
	
	@Override
	/** extract the <b>Value</b> from this 2nd Database from a DatabaseEntry */
	public V readValue(DatabaseEntry key, DatabaseEntry vaue)
			throws DatabaseException {
		return getOwner().readValue(vaue);
		}
	/** encode the <b>Value</b> from this 2nd Database to a DatabaseEntry */
	protected DatabaseEntry valueToEntry(V value) throws DatabaseException
		{
		return getOwner().valueToEntry(value);
		};
		
	@Override
	/** return the BerkeleyDB type of storage for this Database */
	public DatabaseType getDatabaseType() {
		return getOwner().getDatabaseType();
		}
	}

/**
 * Specialized AbstractSecondaryDatabase using a UInt3 as the key 
 * @author lindenb
 *
 * @param <K> key type
 * @param <V> value type
 */
public static abstract class SecondaryDBUInt32<K extends Comparable<K>,V>
extends AbstractSecondaryDatabase<fr.cephb.lang.UInt32,K,V>
	{
	public SecondaryDBUInt32(OperonDB<K,V> owner )
		{
		super(owner);
		}
	
	public List<V> get(long key) throws DatabaseException {
		return get(new UInt32(key));
		}
	
	@Override
	public fr.cephb.lang.UInt32 keyFromString(String s) {
		return new fr.cephb.lang.UInt32(s);
		}
	
	@Override
	protected DatabaseEntry keyToEntry(UInt32 key) throws DatabaseException {
		return new DatabaseEntry(new ByteWriter().writeUInt32(key).getBytes());
		}
	@Override
	public UInt32 readKey(DatabaseEntry key, DatabaseEntry data)
			throws DatabaseException {
		UInt32 k= new UInt32(new ByteReader(key).readUInt32());
		return k;
		}
	}

/**
 * Specialized AbstractSecondaryDatabase using a String as the key 
 * @author lindenb
 *
 * @param <K> key type
 * @param <V> value type
 */
public static abstract class SecondaryDBString<K extends Comparable<K>,V>
extends AbstractSecondaryDatabase<String,K,V>
	{
	public SecondaryDBString(OperonDB<K,V> owner )
		{
		super(owner);
		}
	@Override
	public String keyFromString(String s) {
		return s;
		}
	@Override
	protected DatabaseEntry keyToEntry(String key) throws DatabaseException {
		return new DatabaseEntry(DBUtils.string2CString(key));
		}
	@Override
	public String readKey(DatabaseEntry key, DatabaseEntry data)
			throws DatabaseException {
		return new ByteReader(key).readCString();
		}
	}
/**
 * Specialized AbstractSecondaryDatabase using a Integer as the key 
 * @author lindenb
 *
 * @param <K> key type
 * @param <V> value type
 */
public static abstract class SecondaryDBINTEGER<K extends Comparable<K>,V>
extends AbstractSecondaryDatabase<Integer,K,V>
	{
	public SecondaryDBINTEGER(OperonDB<K,V> owner )
		{
		super(owner);
		}
	
	@Override
	public Integer keyFromString(String s) {
		return new Integer(s);
		}
	
	@Override
	protected DatabaseEntry keyToEntry(Integer key) throws DatabaseException
		{
		ByteWriter w= new ByteWriter();
		w.writeInt32(key);
		return w.getEntry();
		}
	@Override
	public Integer readKey(DatabaseEntry key, DatabaseEntry data)
			throws DatabaseException {
		return new ByteReader(key).readInt32();
		}
	}



/**
 * constructor
 * @param operon
 */
protected OperonDB(Operon operon)
	{
	this.myoperon= operon;
	}

/** get the wrapped database */
public Database getDatabase() {
	return database;
	}


/** get the config for the wrapped database. Will be created if it does not exist */
public DatabaseConfig getDatabaseConfig() throws DatabaseException
	{
	if(this.dbConfig==null)
		{
		this.dbConfig= createConfig();
		}
	return this.dbConfig;
	}

/** create a configuration for this database accoding to getOperon().getMode() */
protected DatabaseConfig createConfig()  throws DatabaseException
	{
	DatabaseConfig cfg = new DatabaseConfig();
	
	switch(getOperon().getMode())
		{
		case READ_ONLY:
			cfg.setType(DatabaseType.UNKNOWN);
			cfg.setAllowCreate(false);
			cfg.setReadOnly(true);
			break;
		case CREATOR:
			cfg.setType(getDatabaseType());
			cfg.setAllowCreate(true);
			cfg.setReadOnly(false);
			break;
		case WRITE:
			cfg.setType(DatabaseType.UNKNOWN);
			cfg.setAllowCreate(false);
			cfg.setReadOnly(false);
			break;
		default: throw new IllegalArgumentException(getOperon().getMode().toString());
		}
	
	cfg.setErrorHandler(new ErrorHandler()
		{
		@Override
		public void error(Environment environment, String errpfx, String msg) {
			System.err.println(""+errpfx+": An error occured for "+getName()+":"+msg);
			}
		});
	
	if(isAllowingDuplicates())
		{
		cfg.setSortedDuplicates(true);
		}
	return cfg;
	}

/** is this database open */
public boolean isOpen()
	{
	return this.database!=null;
	}

/** is this database allowing duplicate keys ? */
public abstract boolean isAllowingDuplicates();

/** open this database if it was not already opened */
public OperonDB<K, V> open() throws DatabaseException,FileNotFoundException
	{
	if(this.database!=null) return this;
	//log().info("opening "+getOperon().getEnvironment().getHome()+"/"+getName()+ " exists="+exists());
	this.database= getOperon().getEnvironment().openDatabase(
			null, getName(), null,
			getDatabaseConfig()
			);
	return this;
	}

/** @return true if a file for this database exists */
public boolean exists()
	{
	try
		{
		File f= new File(getOperon().getEnvironment().getHome(),getName());
		return f.exists();
		}
	catch(DatabaseException err)
		{
		return false;
		}
	}

/** close this database */
public void close() throws DatabaseException
	{
	if(this.database!=null)
		{
		try {
			this.database.close();
		} catch (DatabaseException e) {
			throw e;
		}finally
		{
			this.database=null;
			}
		
		}
	this.database=null;
	}

/** close this database without throwing an exceptio  */
public void safeClose()
	{
	try {
		this.close();
	} catch (Exception e) {
		e.printStackTrace();
		}
	finally
		{
		this.database=null;
		}
	}


@Override
/** call close() */
protected void finalize() throws Throwable {
	close();
	super.finalize();
	}

/** get the Operon log */
public Logger log()
	{
	return getOperon().log();
	}

/** open a new Cursor for this database */
public Cursor cursor(Transaction tx,CursorConfig config) throws DatabaseException
	{
	if(!isOpen()) throw new DatabaseException("Database "+getName()+"is not open");
	return getDatabase().openCursor(tx,config);
	}

/** @return a cursor for this database */
public Cursor cursor() throws DatabaseException
	{
	return cursor(null,null);
	}
/** scan the database using a CursorListener */
public void scanDatabase(CursorListener cl) throws DatabaseException
	{
	Cursor cursor=null;
	DatabaseEntry foundKey = new DatabaseEntry();
    DatabaseEntry foundData = new DatabaseEntry();

	try {
		cursor=cursor();
		 while (cursor.getNext(foundKey, foundData, LockMode.DEFAULT) ==OperationStatus.SUCCESS)
		 	{
			if(!cl.found(foundKey, foundData))
				{
				break;
				}
		 	}
		} 
	finally
		{
		DBUtils.safeClose(cursor);
		}
	}
/** return the Operon environement */
public Operon getOperon()
	{
	return this.myoperon;
	}

/** return <b>ALL</b> the values in this database. Use with care for only the small DB */
public List<V> getValues()  throws DatabaseException
	{
	return getValues(0, Integer.MAX_VALUE);
	}

/** 
 * get values starting from index
 * 
 * @param start the start index (0 based)
 * @param length the number of items
 * @return a Collection of V
 * @throws DatabaseException
 */
public List<V> getValues(int start,int length)  throws DatabaseException
	{
	Vector<V> _items= new Vector<V>();
	if(length<=0) return _items;
	DatabaseEntry key= new DatabaseEntry();
	DatabaseEntry value= new DatabaseEntry();
	Cursor c=null;
	int index=-1;
	try {
		c=cursor();
		while(c.getNext(key, (index+1<start?null:value), LockMode.DEFAULT)==OperationStatus.SUCCESS)
			{
			++index;
			if(index<start) continue;
			_items.addElement(readValue(key,value));
			if(_items.size()>=length) break;
			}
		return _items;
		}
	finally
		{
		DBUtils.safeClose(c);
		}
	}
/** return the relative of this database <b>if</b> it is associated to a given assembly */
protected String getAssemblySpecificPath(Assembly assembly)
	{
	return "dbs/dna/"+assembly.getOrganism().getTreeLetterCode()+"/"+assembly.getVersion()+
		"/"+assembly.getSource();
	}

/** return the relative of this database <b>if</b> it is associated to a given assembly and to a given suffix (e.g CBuildDB )*/
protected String getAssemblySpecificName(Assembly assembly,String suffix)
	{
	return getAssemblySpecificPath(assembly)+"/"+assembly.getOrganism().getTreeLetterCode()+
		"_"+assembly.getSource()+"_"+suffix+"_"+assembly.getVersion()+".bdb"
		;
	}

/** get the partial pathnname of the database relative to the operon base path */
public abstract String getName();
/** convert the key/values pair of DatabaseEntries to the (V)alue */
public abstract V readValue(DatabaseEntry key,DatabaseEntry data)  throws DatabaseException;
/** convert the key/values pair of DatabaseEntries to the (K)ey */
public abstract K readKey(DatabaseEntry key,DatabaseEntry data)  throws DatabaseException;
/** convert a (V)alue to a DatabaseEntry */
protected abstract DatabaseEntry valueToEntry(V value)  throws DatabaseException;
/** convert a (K)key to a DatabaseEntry */
protected abstract DatabaseEntry keyToEntry(K key)  throws DatabaseException;

/** extract the <b>Value</b> from a DatabaseEntry */
public final V readValue(DatabaseEntry data)  throws DatabaseException
	{
	return readValue(null,data);
	}
/** extract the <b>Key</b> from a DatabaseEntry */
public final K readKey(DatabaseEntry key)  throws DatabaseException
	{
	return readKey(key,null);
	}
/** return wether this database contain this key */
public boolean containsKey(K key)  throws DatabaseException
	{
	return containsKey(keyToEntry(key));
	}

/** return wether this database contain this entry as a key */
protected boolean containsKey(DatabaseEntry key) throws DatabaseException
	{
	Cursor c=null;
	try {
		c=cursor();
		DatabaseEntry value= new DatabaseEntry();
		value.setPartial(0,0,true);
		if(c.getSearchKey(key, value, LockMode.DEFAULT)==OperationStatus.SUCCESS)
			{
			return true;
			}
		return false;
		}
	finally
		{
		DBUtils.safeClose(c);
		}
	}




/** @return a value from a given key or null if not found */
protected DatabaseEntry get(DatabaseEntry key) throws DatabaseException
	{
	Cursor c=null;
	try {
		c=cursor();
		DatabaseEntry value= new DatabaseEntry();
		if(c.getSearchKey(key, value, LockMode.DEFAULT)==OperationStatus.SUCCESS)
			{
			return value;
			}
		return null;
		}
	finally
		{
		DBUtils.safeClose(c);
		}
	}

/** safely close a OperonDB without throwing an exception */
public static void safeClose(OperonDB<?,?> db)
	{
	if(db==null) return;
	try {
		db.close();
	} catch (Exception e) {
		//ignore
		}
	}

/** 
 * an iterator over key/values as DatabaseEntry
 * the iterator must be closed
 * @return an iterator over key/values */
public DatabaseEntriesIterator listDatabaseEntries() throws DatabaseException
	{
	return new DatabaseEntriesIterator(cursor());
	}




/** 
 * an iterator over values
 * the iterator must be closed
 * @return an iterator over values */
public ValueIterator listValues() throws DatabaseException
	{
	return new ValueIterator(listDatabaseEntries());
	}


/** 
 * an iterator over key and values
 * the iterator must be closed
 * @return an iterator over values */
public KeyValueIterator listKeyValues() throws DatabaseException
	{
	return new BetweenIterator();
	}

/** 
 * an iterator over key/values as DatabaseEntry
 * the iterator must be closed
 * as far as I know, this range can only be performerd on String as you can only order
 * an array of bytes for Strings
 * @param start the start key 
 * @param end the end key
 * @param includeEnd a boolean specifiying if the end key should be included
 * @return an iterator over key/values */
public BetweenIterator listDatabaseEntries(K start, K end , boolean includeEnd) throws DatabaseException
	{
	return new BetweenIterator(
			start,
			end,
			includeEnd);
	}

/** 
 * an iterator over key and values between a fixed range of key the iterator must be closed
 * As far as I now, this key range only works with string
 * as a byte comparaison only works for ordering Strings
 * @param start the start key 
 * @param end the end key
 * @param includeEnd a boolean specifiying if the end key should be included
 * @return an iterator over values */
public KeyValueIterator listKeyValues(K start,K end, boolean includeEnd) throws DatabaseException
	{
	return listDatabaseEntries(start,end,includeEnd);
	}


/**
 * implements Iterable
 * call <code>listKeyValues</code>
 * will throw a RuntimeException if a DatabaseException is internaly throwed
 */
@Override
public Iterator<Pair<K, V>> iterator()
	{
	try {
		return listKeyValues();
		} 
	catch (DatabaseException e) {
		throw new RuntimeException(e);
		}
	}

/**
 * @return the maximum value of the key, or null if there the db is empty
 */
public K getKeyMax() throws DatabaseException
	{	
	K max=null;
	KeyValueIterator iter=listKeyValues();
	while(iter.hasNext())
		{
		K value= iter.next().first();
		if(max==null || max.compareTo(value)<0)
			{
			max=value;
			}
		}
	iter.close();
	return max;
	}




/**
 * Transform a String e.g. a 'user query as text' to <code>K</code>
 * @param s the string as input
 * @return null if the transformation failed
 */
public abstract K keyFromString(String s);

/** returns the database type*/
public abstract DatabaseType getDatabaseType();
}
